import stripe
from django.db import transaction
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.exceptions import ValidationError
from rest_framework.response import Response
from rest_framework_simplejwt.authentication import JWTAuthentication

from apps.client.models import Client
from apps.client.types import ClientStatusChoices
from apps.payment.helpers import create_stripe_id
from apps.payment.models import ClientStripeAccount
from apps.users.models import Provider
from apps.users.permissions import IsProvider, IsClient
from apps.users.types import ProviderStatusTypeChoices
from django.conf import settings

stripe.api_key = settings.STRIPE_SECRET_KEY


@api_view(('GET', 'POST'))
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider])
@transaction.atomic
def provider_stripe_api(request):
    if request.method == 'POST':
        data = request.data
        provider = request.user.provider

        if provider.stripe_id is not None:
            raise ValidationError('Provider already exist')

        provider.stripe_id = create_stripe_id(provider, data)
        provider.status = ProviderStatusTypeChoices.ACTIVE.value

        provider.save()

        return Response({'stripe_id': str(provider.stripe_id)}, status=status.HTTP_201_CREATED)


@api_view(('GET', 'POST'))
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider | IsClient])
@transaction.atomic
def card_list_api(request):
    if request.method == 'GET':
        if request.user.is_provider:
            provider = Provider.objects.get(id=request.user.id)
            print(provider.first_name)
            print(provider.stripe_id)
            if provider.stripe_id is None:
                return Response([], status.HTTP_200_OK)

            stripe_id = provider.stripe_id
            sources = stripe.Customer.list_sources(
                stripe_id,
                object="card",
                limit=100,
            )

        elif request.user.is_client:
            client = get_object_or_404(ClientStripeAccount, client__id=request.user.id)
            if client is None:
                return Response([], status.HTTP_200_OK)

            stripe_id = client.stripe_id
            sources = stripe.Customer.list_sources(
                stripe_id,
                object="card",
                limit=100,
            )

        return Response(sources.data, status=status.HTTP_200_OK)

    if request.method == 'POST':
        try:
            if request.user.is_provider:
                provider = Provider.objects.get(id=request.user.id)
                stripe_customer = check_or_create_stripe_customer(provider, request)
                provider.stripe_id = stripe_customer.id
                provider.save()
                print(provider.stripe_id)
                token = stripe.Token.create(
                    card={
                        "number": request.data.get('card_number'),
                        "exp_month": request.data.get('exp_month'),
                        "exp_year": request.data.get('exp_year'),
                        "cvc": request.data.get('card_cvc'),
                        "address_zip": request.data.get('zip_code')
                    }
                )

                new_card = add_card_to_customer(
                    stripe_customer,
                    token,
                    request
                )

                if provider.status != ProviderStatusTypeChoices.ACTIVE.value:
                    provider.status = ProviderStatusTypeChoices.ACTIVE.value
                    provider.save()
            elif request.user.is_client:
                auto_pay = request.data.get('auto_pay', False)
                client = Client.objects.get(id=request.user.id)
                stripe_customer = check_or_create_stripe_customer(client, request)
                token = stripe.Token.create(
                    card={
                        "number": request.data.get('card_number'),
                        "exp_month": request.data.get('exp_month'),
                        "exp_year": request.data.get('exp_year'),
                        "cvc": request.data.get('card_cvc'),
                        "address_zip": request.data.get('zip_code')
                    }
                )
                new_card = add_card_to_customer(
                    stripe_customer,
                    token,
                    request
                )
                if auto_pay:
                    client_stripe = get_object_or_404(ClientStripeAccount, client__id=request.user.id)
                    if client_stripe:
                        client_stripe.auto_pay = auto_pay
                        client_stripe.auto_pay_card_id = new_card.id
                        client_stripe.save()

                if client.status != ClientStatusChoices.ACTIVE.value:
                    client.status = ClientStatusChoices.ACTIVE.value
                    client.save()

            return Response(new_card, status=status.HTTP_200_OK)
        except stripe.error.StripeError as e:
            raise ValidationError('Card is invalid')


def check_or_create_stripe_customer(instance, request):
    if instance.is_client:
        stripe_account = instance.stripe_account_id if hasattr(
            instance, 'stripe_account_id') else None
    else:
        stripe_account = instance.stripe_id if hasattr(
            instance, 'stripe_id') else None
    auto_pay = request.data.get('auto_pay', False)
    if instance.is_provider:
        if instance.provider.stripe_id is None:
            new_account = stripe.Customer.create(
                email=instance.email
            )
            provider = instance.provider
            provider.stripe_id = new_account.id
            provider.save()
            return new_account
        else:
            return instance.provider.stripe_id
    if instance.is_client:
        if instance.stripe_account_id is None:
            new_account = stripe.Customer.create(
                email=instance.email
            )
            stripe_account = ClientStripeAccount.objects.create(
                client=instance,
                stripe_id=new_account.id,
                auto_pay=auto_pay
            )
            instance.stripe_account_id = new_account.id
            instance.save()
        else:
            stripe_account = ClientStripeAccount.objects.filter().first()

    return stripe_account


def add_card_to_customer(stripe_customer, card_token, request):
    if isinstance(stripe_customer, str):
        stripe_customer = stripe_customer
    else:
        stripe_customer = stripe_customer.stripe_id
    new_source = stripe.Customer.create_source(
        stripe_customer,
        source=card_token,
    )
    default_payment = request.data.get('default_payment', False)
    if default_payment:
        stripe.Customer.modify(
            stripe_customer,
            default_source=new_source.id
        )

    return new_source


@api_view(['DELETE'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider | IsClient])
@transaction.atomic
def card_detail_api(request, card_id):
    if request.method == 'DELETE':
        user = request.user
        if request.user.is_provider:
            provider = user.provider
            stripe.Customer.delete_source(
                provider.stripe_id,
                card_id
            )
            payment_methods = stripe.PaymentMethod.list(
                customer=provider.stripe_id,
                type='card')
            stripe_result = stripe.Customer.retrieve(provider.stripe_id)
            stripe_result['sources'] = payment_methods.data
            if len(stripe_result['sources']) == 0:
                provider.status = ProviderStatusTypeChoices.INACTIVE.value
                provider.stripe_id = None
                provider.save()

        elif request.user.is_client:
            client = Client.objects.get(id=request.user.id)
            customer = get_object_or_404(ClientStripeAccount, client=client)

            stripe.Customer.delete_source(customer.stripe_id, card_id)
            payment_methods = stripe.PaymentMethod.list(
                customer=customer.stripe_id,
                type='card')
            stripe_result = stripe.Customer.retrieve(customer.stripe_id)
            stripe_result['sources'] = payment_methods.data
            if len(stripe_result['sources']) == 0:
                client.status = ClientStatusChoices.INACTIVE.value
                client.save()
                customer.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


@api_view(('GET', 'POST', 'DELETE'))
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider | IsClient])
@transaction.atomic
def connect_stripe_account(request):
    if request.method == 'GET':
        if request.user.is_provider:
            provider = Provider.objects.get(id=request.user.id)
            if provider.stripe_account_id is None:
                return Response(status=status.HTTP_204_NO_CONTENT)
            stripe_result = stripe.Account.retrieve(provider.stripe_account_id)

        elif request.user.is_client:
            client = Client.objects.get(id=request.user.id)
            if client.stripe_account_id is None:
                return Response(status=status.HTTP_204_NO_CONTENT)

            stripe_result = stripe.Account.retrieve(client.stripe_account_id)

        return Response(stripe_result, status=status.HTTP_200_OK)

    if request.method == 'POST':
        try:
            data = request.data
            if 'code' not in data or data['code'] is None:
                raise ValidationError('Token is invalid')
            response = stripe.OAuth.token(
                grant_type='authorization_code',
                code=data['code']
            )
            connected_account_id = response['stripe_user_id']

            if request.user.is_provider:
                Provider.objects \
                    .filter(id=request.user.id) \
                    .update(stripe_account_id=connected_account_id)

            elif request.user.is_client:
                Client.objects \
                    .filter(id=request.user.id) \
                    .update(stripe_account_id=connected_account_id)

            return Response(status=status.HTTP_202_ACCEPTED)

        except stripe.error.StripeError as e:
            return Response(e.error, status=status.HTTP_400_BAD_REQUEST)

    if request.method == 'DELETE':
        if request.user.is_provider:
            provider = Provider.objects.get(id=request.user.id)
            stripe_result = None
            try:
                stripe_result = stripe.Account.delete(provider.stripe_account_id)
            except:
                pass

            Provider.objects.filter(id=provider.id).update(stripe_account_id=None)

        elif request.user.is_client:
            client = Client.objects.get(id=request.user.id)
            stripe_result = None
            try:
                stripe_result = stripe.Account.delete(client.stripe_account_id)
            except:
                pass

            Client.objects.filter(id=client.id).update(stripe_account_id=None)

        return Response(stripe_result, status=status.HTTP_202_ACCEPTED)

@api_view(('GET', 'POST', 'DELETE'))
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider | IsClient])
@transaction.atomic
def provider_stripe_view_api(request):
    if request.method == 'GET':
        user = request.user
        try:
            payment_methods = stripe.PaymentMethod.list(
                customer=user.provider.stripe_id,
                type='card'
            )
            stripe_result = {}
            if user.provider.stripe_id:
                stripe_result = stripe.Customer.retrieve(user.provider.stripe_id)
                stripe_result['sources'] = payment_methods.data
        except:
            payment_methods = stripe.PaymentMethod.list(
                customer=user.provider.stripe_id,
                type='card'
            )
            stripe_result = {}
            if user.provider.stripe_id:
                stripe_result = stripe.Customer.retrieve(user.provider.stripe_id)
                stripe_result['sources'] = payment_methods.data
        return Response(stripe_result, status=status.HTTP_200_OK)


@api_view(['GET'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider])
@transaction.atomic
def provider_stripe_status(request):
    if request.method == 'GET':
        try:
            get_user = request.user
            if get_user.provider.stripe_account_id:
                try:
                    payment_methods = stripe.PaymentMethod.list(
                        customer=get_user.provider.stripe_account_id,
                        type='card'
                    )
                    get_account = stripe.Customer.retrieve(get_user.provider.stripe_account_id)
                    get_account['sources'] = payment_methods.data
                    # if get_account.details_submitted:
                    #     login_data = stripe.Account.create_login_link(get_user.provider.stripe_account_id)
                    #     login_up_link = login_data.url
                except:
                    get_account = stripe.Account.retrieve(id=get_user.provider.stripe_account_id)
                    if get_account.details_submitted:
                        login_data = stripe.Account.create_login_link(get_user.provider.stripe_account_id)
                        login_up_link = login_data.url
                        return Response({
                            "connected": True,
                            "login_stripe_url": login_up_link
                        }, status=status.HTTP_200_OK)
                return Response(get_account, status=status.HTTP_200_OK)
            return Response({
                "connected": False
            }, status=status.HTTP_200_OK)
        except Exception as e:
            return Response({
                'success': False,
                'message': f'Failed to get provider stipe account status because of {e}'
            }, status=status.HTTP_400_BAD_REQUEST)


@api_view(['POST'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider | IsClient])
@transaction.atomic
def connect_stripe_account_api(request):
    try:
        validate_data = {'refresh_url': request.data.get('refresh_url'),
                         'return_url': request.data.get('return_url')}
        none_keys = [key for key, value in validate_data.items() if value is None]
        if none_keys:
            return Response({'success': False, 'message': f"{', '.join(none_keys)} "
                                                          f"{'is' if len(none_keys) == 1 else 'are'} required"}
                            , status.HTTP_400_BAD_REQUEST)
        account_created = False
        if request.user.is_provider:
            get_stripe_id = request.user.provider.stripe_account_id
            if get_stripe_id:
                get_account = stripe.Account.retrieve(id=get_stripe_id)
                if not get_account.details_submitted:
                    sign_up_link = stripe.AccountLink.create(account=get_stripe_id,
                                                             refresh_url=validate_data['refresh_url'],
                                                             return_url=validate_data['return_url'],
                                                             type='account_onboarding')
                    account_created = True
                else:
                    login_data = stripe.Account.create_login_link(get_stripe_id)
                    sign_up_link = login_data.url
                    account_created = True
        elif request.user.is_client:
            get_stripe_id = request.user.client.stripe_account_id
            if get_stripe_id:
                get_account = stripe.Account.retrieve(id=get_stripe_id)
                if not get_account.details_submitted:
                    sign_up_link = stripe.AccountLink.create(account=get_stripe_id,
                                                             refresh_url=validate_data['refresh_url'],
                                                             return_url=validate_data['return_url'],
                                                             type='account_onboarding')
                    account_created = True
                else:
                    login_data = stripe.Account.create_login_link(get_stripe_id)
                    sign_up_link = login_data.url
                    account_created = True
        if not account_created:
            get_account = stripe.Account.create(type="express", email=request.user.email)
            sign_up_link = stripe.AccountLink.create(account=get_account, refresh_url=validate_data['refresh_url'],
                                                     return_url=validate_data['return_url'], type='account_onboarding')
            if request.user.is_provider:
                Provider.objects \
                    .filter(id=request.user.id) \
                    .update(stripe_account_id=get_account.id)

            elif request.user.is_client:
                Client.objects \
                    .filter(id=request.user.id) \
                    .update(stripe_account_id=get_account.id)
        return Response({
            'success': True,
            'message': 'Stripe account created successfully',
            'sign_up_link': sign_up_link['url']
        }, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            'success': False,
            'message': f'Failed to connect stripe account because of {str(e)}'
        }, status=status.HTTP_400_BAD_REQUEST)


@api_view(['POST'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsProvider])
@transaction.atomic
def change_default_api(request):
    try:
        provider = request.user.provider

        stripe_result = stripe.Customer.modify(
            provider.stripe_id,
            default_source=request.data['default_source']
        )

        return Response(stripe_result, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            'success': False,
            'message': f'Failed to set by-default payment method because of {str(e)}'
        }, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsClient])
# @transaction.atomic
def list_stripe_customer(request):
    try:
        if request.method == 'GET':
            client = request.user
            customer = ClientStripeAccount.objects.filter(client=client).first()
            if not customer:
                return Response({
                    'success': False,
                    'message': f'Client stripe account does not exist'
                }, status=status.HTTP_404_NOT_FOUND)

            payment_methods = stripe.PaymentMethod.list(
                customer=customer.stripe_id,
                type='card'
            )
            stripe_result = stripe.Customer.retrieve(customer.stripe_id)
            stripe_result['sources'] = payment_methods.data
            stripe_result['auto_pay'] = customer.auto_pay
            if stripe_result['auto_pay']:
                stripe_result['auto_pay_card_id'] = customer.auto_pay_card_id

            return Response(stripe_result, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            'success': False,
            'message': f'Failed to get list of stripe customer because of {str(e)}'
        }, status=status.HTTP_400_BAD_REQUEST)


@api_view(['POST'])
@authentication_classes([JWTAuthentication])
@permission_classes([IsClient])
@transaction.atomic
def change_default_payment(request):
    try:
        if request.method == 'POST':
            client = request.user
            customer = get_object_or_404(ClientStripeAccount, client=client)
            stripe_result = {}

            if 'default_source' in request.data:
                stripe_result = stripe.Customer.modify(
                    customer.stripe_id,
                    default_source=request.data['default_source']
                )
            if 'auto_pay' in request.data:
                customer.auto_pay = request.data['auto_pay']
                if customer.auto_pay:
                    customer.auto_pay_card_id = request.data['auto_pay_card_id']
                else:
                    customer.auto_pay_card_id = ""
                customer.save()

            return Response(stripe_result, status=status.HTTP_200_OK)
    except Exception as e:
        return Response({
            'success': False,
            'message': f'Failed to change by-default payment because of {str(e)}'
        }, status=status.HTTP_400_BAD_REQUEST)